package org.jslaughter.component.physics.effect;

import org.jbox2d.collision.Segment;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.RaycastResult;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.World;
import org.jslaughter.event.eventtype.DamageEvent;
import org.jslaughter.event.eventtype.ExplosionEvent;

import org.nvframe.component.AbstractComponent;
import org.nvframe.component.physics.Physics;
import org.nvframe.component.state.Position;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.SoundEvent;
import org.nvframe.event.eventtype.UpdateEvent;
import org.nvframe.event.eventtype.UpdateListener;
import org.nvframe.manager.ConfigManager;
import org.nvframe.manager.RegisterManager;
import org.nvframe.util.settings.SettingsObj;

public class BasicExplosion extends AbstractComponent implements UpdateListener {
	
	private World world;
	private boolean exploded = false;
	
	// use raycast to test for walls?
	private boolean useRaycast;
	// maximum distance in pixels
	private float radius;
	// maximum "kickback" force (only physical, doesnt affect the health)
	private float maxForce;
	private int maxDamagePoints;
	private String explosionSound;

	public BasicExplosion(String id, Entity owner, SettingsObj settings) {
		super(id, owner);
		
		world = (World) RegisterManager.getInstance().getOption("world");
		
		radius = settings.getFloat("radius", 100f) / Physics.PIXELS_PER_METER;
		maxForce = settings.getFloat("maxForce", 200f);
		maxDamagePoints = settings.getInt("maxDamagePoints", 0);
		useRaycast = settings.getBoolean("useRaycast", true);
		explosionSound = settings.getString("explosionSound", null);
		
		EventService.getInstance().addEventListener(this);
	}
	
	public void doExplosion() {
		float force;
		float angle;
		float distance;
		float strength;
		int damagePoints;
		Entity receiver;
		
		Position posComp = (Position) owner.getComponent(Position.class);
		Vec2 position = new Vec2(posComp.getX() / Physics.PIXELS_PER_METER, posComp.getY() / Physics.PIXELS_PER_METER);
		
		for (Body body = world.getBodyList(); body.getNext() != null; body = body.getNext()) {
			if (!body.isDynamic() || body.isBullet())
				continue;
			
			// raycast test (target behind a wall?)
			if(useRaycast) {
				RaycastResult result = new RaycastResult();
				Segment segment = new Segment();
				segment.p1.set(position.x, position.y);
				segment.p2.set(body.getPosition().x, body.getPosition().y);
				
				Shape shape = world.raycastOne(segment, result, false, null);
				if(shape == null)
					continue;
				
				Body hitBody = shape.getBody();
				
				// TODO: entity groups => if entitygroup == wall => not affected
				if(((Entity) hitBody.getUserData()).getId().equals("ent_wall1"))
					continue;
			}
			
			// wake up the body for physics
			body.wakeUp();
				
			Vec2 bodyPos = new Vec2(body.getPosition().x, body.getPosition().y);

			distance = (float) Math.sqrt(Math.pow(body.getPosition().x - position.x, 2) + 
					Math.pow(body.getPosition().y - position.y, 2));
			
			if(distance > radius)
				continue;

			strength = (radius - distance) / radius;
			force = strength * maxForce;
			
			angle = (float) Math.atan2(bodyPos.y - position.y, bodyPos.x - position.x);

			// apply an impulse to the body, using the angle
			body.applyImpulse(new Vec2((float) Math.cos(angle) * force, (float) Math.sin(angle) * force), body.getWorldCenter());
			
			// calculate the damage to the entity TODO: improve round (maby damage must be a float?)
			damagePoints = Math.round(strength * maxDamagePoints);
			
			if(ConfigManager.getInstance().getActive("debugMode"))
				System.out.println("explosion affected: " + ((Entity) body.getUserData()).getId() +
						", distance: " +  distance + " meters, damagePoints: " + damagePoints);
			
			if(damagePoints > 0) {
				receiver = (Entity) body.getUserData();
				EventService.getInstance().fireEvent(new DamageEvent(owner, receiver, damagePoints));
				EventService.getInstance().fireEvent(new ExplosionEvent(owner, receiver, damagePoints));
			}
		}
	}

	@Override
	public void onUpdate(UpdateEvent event) {
		if(exploded)
			return;
		
		// explode on first update step so all other components are loaded
		doExplosion();
		
		// play explosion sound
		if(explosionSound != null)
			EventService.getInstance().fireEvent(new SoundEvent(explosionSound));
		
		exploded = true;
		
		EventService.getInstance().removeEventListener(this);
	}
	
}
